﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.Diagnostics;
using Microsoft.WindowsAzure.ServiceRuntime;
using System.Data.Sql;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Data;

using Microsoft.Practices.EnterpriseLibrary.WindowsAzure.TransientFaultHandling.SqlAzure;

namespace ViewRunSim
{
    public static class utils 
    {
        public static Random rand = new Random(DateTime.Now.Millisecond);
        public static double ExponentialRand(double lambda)
        {
            return -1.0 / lambda * Math.Log(rand.NextDouble());
        }
    }
    public class LicenseSim 
    {
        double entryRate;
        double[] paperRates;
        double[] photoRates;
        string connString;
        double duration;

        public enum TrialState{up,probation,failed,notrunning};

        public static List<TrialState> states=new List<TrialState>(); 

        public LicenseSim(double entryRate,double[] paperRates,double[] photoRates,double duration,string connString) 
        {
            this.entryRate = entryRate;
            this.paperRates = paperRates;
            this.photoRates = photoRates;
            this.connString = connString;
            this.duration = duration;
        }

        public int RunSim() 
        {
            return RunSim(this.entryRate, this.paperRates, this.photoRates, this.duration, this.connString);
        }

        public static int RunSim(double entryRate, double[] paperRates, double[] photoRates, double duration, string connString) 
        {
            //Create Trial

            ReliableSqlConnection sqlConn = new ReliableSqlConnection(connString,  Microsoft.Practices.TransientFaultHandling.RetryPolicy.DefaultFixed,Microsoft.Practices.TransientFaultHandling.RetryPolicy.DefaultFixed);
            sqlConn.ConnectionRetryPolicy.Retrying += new EventHandler<Microsoft.Practices.TransientFaultHandling.RetryingEventArgs>(ConnRetryPolicy_Retrying);
            sqlConn.CommandRetryPolicy.Retrying += new EventHandler<Microsoft.Practices.TransientFaultHandling.RetryingEventArgs>(CommandRetryPolicy_Retrying);

            try
            {
                sqlConn.Open();
                //Log trial information
                String cmdText = @"INSERT INTO trials(name,description,startClockTime,endClockTime,duration) VALUES('licensing','test',GETUTCDATE(),NULL,@duration);SELECT SCOPE_IDENTITY() 'scopeIdentity'";
                SqlCommand cmd = new SqlCommand(cmdText);
                cmd.Parameters.AddWithValue("@duration", duration);
                int trialID = sqlConn.ExecuteCommand<int>(cmd);
                //Seed the trial

                int iter = 1;
                //Add to queue
                cmdText = @"INSERT INTO trialState(trialID,iteration,currTime,queue1length,queue2length,nextEntry,nextPaper1,nextPaper2,nextPaper3,nextPaper4,nextPhoto1,nextPhoto2) VALUES(@trialID,@iteration,@currTime,0,0,@nextEntry,@nextPaper1,@nextpaper2,@nextPaper3,@nextPaper4,@nextPhoto1,@nextPhoto2)";
                cmd = new SqlCommand(cmdText);
                cmd.Parameters.AddWithValue("@trialID", trialID);
                cmd.Parameters.AddWithValue("@iteration", iter);
                cmd.Parameters.AddWithValue("@currTime", 0);
                cmd.Parameters.AddWithValue("@nextEntry", utils.ExponentialRand(entryRate));
                cmd.Parameters.AddWithValue("@nextPaper1", 0);
                cmd.Parameters.AddWithValue("@nextPaper2", 0);
                cmd.Parameters.AddWithValue("@nextPaper3", 0);
                cmd.Parameters.AddWithValue("@nextPaper4", 0);
                cmd.Parameters.AddWithValue("@nextPhoto1", 0);
                cmd.Parameters.AddWithValue("@nextPhoto2", 0);
                using (IDataReader rdr = sqlConn.ExecuteCommand<IDataReader>(cmd)) ;


                double currTime = 0, nextEntry, nextPaper1, nextPaper2, nextPaper3, nextPaper4, nextPhoto1, nextPhoto2 = 0;
                //Begin the simulation loop
                while (currTime < duration)
                {
                    cmdText = "SELECT currTime,nextEntry,nextPaper1,nextPaper2,nextPaper3,nextPaper4,nextPhoto1,nextPhoto2,queue1Length,queue2length FROM trialState WHERE iteration=" + iter + " and trialID=" + trialID + " ORDER BY iteration desc";
                    cmd = new SqlCommand(cmdText);

                    int queue1Length, queue2Length;
                    using (IDataReader rdr = sqlConn.ExecuteCommand<IDataReader>(cmd))
                    {
                        rdr.Read();
                        nextEntry = (double)rdr["nextEntry"];
                        nextPaper1 = (double)rdr["nextPaper1"];
                        nextPaper2 = (double)rdr["nextPaper2"];
                        nextPaper3 = (double)rdr["nextPaper3"];
                        nextPaper4 = (double)rdr["nextPaper4"];
                        nextPhoto1 = (double)rdr["nextPhoto1"];
                        nextPhoto2 = (double)rdr["nextPhoto2"];
                        queue1Length = (int)rdr["queue1Length"];
                        queue2Length = (int)rdr["queue2Length"];
                        currTime = (double)rdr["currTime"];
                    }
                    iter++;

                    //Load the state
                    Dictionary<string, double> stateVars = new Dictionary<string, double> { { "nextEntry", nextEntry }, { "nextPaper1", nextPaper1 }, { "nextPaper2", nextPaper2 }, { "nextPaper3", nextPaper3 }, { "nextPaper4", nextPaper4 }, { "nextPhoto1", nextPhoto1 }, { "nextPhoto2", nextPhoto2 } };
                    KeyValuePair<string, double> maxPair = new KeyValuePair<string, double>("", Int64.MaxValue);

                    foreach (KeyValuePair<string, double> kvp in stateVars)
                    {
                        if (kvp.Value <= maxPair.Value && kvp.Value > 0)
                            maxPair = kvp;
                    }
                    currTime = maxPair.Value;

                    //State transition

                    //If queue entry, either add to queue or move directly to open agent
                    if (maxPair.Key == "nextEntry")
                    {
                        if (stateVars["nextPaper1"] == 0)
                        {//schedule the paper agent to handle the guest
                            stateVars["nextPaper1"] = currTime + utils.ExponentialRand(paperRates[0]);
                        }
                        else
                        {
                            if (stateVars["nextPaper2"] == 0)
                            {
                                stateVars["nextPaper2"] = currTime + utils.ExponentialRand(paperRates[1]);
                            }
                            else
                            {
                                if (stateVars["nextPaper3"] == 0)
                                {
                                    stateVars["nextPaper3"] = currTime + utils.ExponentialRand(paperRates[2]);
                                }
                                else
                                {
                                    if (stateVars["nextPaper4"] == 0)
                                    {
                                        stateVars["nextPaper4"] = currTime + utils.ExponentialRand(paperRates[3]);
                                    }
                                    else
                                    {
                                        queue1Length = queue1Length + 1;
                                    }
                                }
                            }
                        }
                        stateVars["nextEntry"] = currTime + utils.ExponentialRand(entryRate); // schedule another entry
                    }

                    //IF paper agent complete, move to second queue or photo agent, accept new guest if possible from queue
                    if (maxPair.Key == "nextPaper1") //PaperAgent1 complete
                    {
                        stateVars["nextPaper1"] = 0;
                        if (stateVars["nextPhoto1"] == 0)
                        {
                            stateVars["nextPhoto1"] = currTime + utils.ExponentialRand(photoRates[0]);
                        }
                        else
                        {
                            if (stateVars["nextPhoto2"] == 0)
                            {
                                stateVars["nextPhoto2"] = currTime + utils.ExponentialRand(photoRates[1]);
                            }
                            else
                            {
                                queue2Length = queue2Length + 1;
                            }
                        }
                        //accept new guest if possible
                        if (queue1Length > 0)
                        {
                            queue1Length--;
                            stateVars["nextPaper1"] = currTime + utils.ExponentialRand(paperRates[0]);
                        }
                    }

                    if (maxPair.Key == "nextPaper2") //PaperAgent1 complete
                    {
                        stateVars["nextPaper2"] = 0;
                        if (stateVars["nextPhoto1"] == 0)
                        {
                            stateVars["nextPhoto1"] = currTime + utils.ExponentialRand(photoRates[0]);
                        }
                        else
                        {
                            if (stateVars["nextPhoto2"] == 0)
                            {
                                stateVars["nextPhoto2"] = currTime + utils.ExponentialRand(photoRates[1]);
                            }
                            else
                            {
                                queue2Length = queue2Length + 1;
                            }
                        }
                        if (queue1Length > 0)
                        {
                            queue1Length--;
                            stateVars["nextPaper2"] = currTime + utils.ExponentialRand(paperRates[1]);
                        }
                    }
                    if (maxPair.Key == "nextPaper3") //PaperAgent1 complete
                    {
                        stateVars["nextPaper3"] = 0;
                        if (stateVars["nextPhoto1"] == 0)
                        {
                            stateVars["nextPhoto1"] = currTime + utils.ExponentialRand(photoRates[0]);
                        }
                        else
                        {
                            if (stateVars["nextPhoto2"] == 0)
                            {
                                stateVars["nextPhoto2"] = currTime + utils.ExponentialRand(photoRates[1]);
                            }
                            else
                            {
                                queue2Length = queue2Length + 1;
                            }
                        }
                        if (queue1Length > 0)
                        {
                            queue1Length--;
                            stateVars["nextPaper3"] = currTime + utils.ExponentialRand(paperRates[2]);
                        }
                    }

                    if (maxPair.Key == "nextPaper4") //PaperAgent1 complete
                    {
                        stateVars["nextPaper4"] = 0;
                        if (stateVars["nextPhoto1"] == 0)
                        {
                            stateVars["nextPhoto1"] = currTime + utils.ExponentialRand(photoRates[0]);
                        }
                        else
                        {
                            if (stateVars["nextPhoto2"] == 0)
                            {
                                stateVars["nextPhoto2"] = currTime + utils.ExponentialRand(photoRates[1]);
                            }
                            else
                            {
                                queue2Length = queue2Length + 1;
                            }
                        }
                        if (queue1Length > 0)
                        {
                            queue1Length--;
                            stateVars["nextPaper4"] = currTime + utils.ExponentialRand(paperRates[3]);
                        }
                    }
                    //If photo agent complete, accept new guest if possible from queue
                    if (maxPair.Key == "nextPhoto1") //photoAgent1 complete
                    {
                        stateVars["nextPhoto1"] = 0;
                        if (queue2Length > 0)
                        {
                            queue2Length--;
                            stateVars["nextPhoto1"] = currTime + utils.ExponentialRand(photoRates[0]);
                        }
                    }
                    if (maxPair.Key == "nextPhoto2") //PaperAgent1 complete
                    {
                        stateVars["nextPhoto2"] = 0;
                        if (queue2Length > 0)
                        {
                            queue2Length--;
                            stateVars["nextPhoto2"] = currTime + utils.ExponentialRand(photoRates[1]);
                        }
                    }

                    //State Transitioned, Update State table in SQL DB
                    cmdText = @"INSERT INTO trialState(trialID,iteration,currTime,queue1Length,queue2Length,nextEntry,nextPaper1,nextPaper2,nextPaper3,nextPaper4,nextPhoto1,nextPhoto2) 
                                        VALUES(@trialID,@iteration,@currTime,@queue1Length,@queue2Length,@nextEntry,@nextPaper1,@nextPaper2,@nextPaper3,@nextPaper4,@nextPhoto1,@nextPhoto2)";
                    cmd = new SqlCommand(cmdText);

                    cmd.Parameters.AddWithValue("@trialID", trialID);
                    cmd.Parameters.AddWithValue("@iteration", iter);
                    cmd.Parameters.AddWithValue("@currTime", maxPair.Value);
                    cmd.Parameters.AddWithValue("@queue1Length", (int)queue1Length);
                    cmd.Parameters.AddWithValue("@queue2Length", (int)queue2Length);
                    cmd.Parameters.AddWithValue("@nextEntry", stateVars["nextEntry"]);
                    cmd.Parameters.AddWithValue("@nextPaper1", stateVars["nextPaper1"]);
                    cmd.Parameters.AddWithValue("@nextPaper2", stateVars["nextPaper2"]);
                    cmd.Parameters.AddWithValue("@nextPaper3", stateVars["nextPaper3"]);
                    cmd.Parameters.AddWithValue("@nextPaper4", stateVars["nextPaper4"]);
                    cmd.Parameters.AddWithValue("@nextPhoto1", stateVars["nextPhoto1"]);
                    cmd.Parameters.AddWithValue("@nextPhoto2", stateVars["nextPhoto2"]);
                    using (IDataReader rdr = sqlConn.ExecuteCommand<IDataReader>(cmd)) ;
                    states[0] = TrialState.up;
                    
                }
            }
            catch (Exception e)
            {
                Trace.WriteLine(@"Simulation failed: " + e);
            }
            finally 
            {
                sqlConn.Close();
            }

            return 0;
        }

        static void CommandRetryPolicy_Retrying(object sender, Microsoft.Practices.TransientFaultHandling.RetryingEventArgs e)
        {
            if (e.LastException is SqlException)
            {
                string sqlErrors = "";
                foreach (SqlError err in ((SqlException)(e.LastException)).Errors)
                    sqlErrors += @"Number: " + err.Number + @" Msg: " + err.Message + @"\n";
                System.Diagnostics.Trace.WriteLine(@"Retrying Command, Failed connecting to: " + ((SqlException)(e.LastException)).Server + "Last Exception: " + ((SqlException)(e.LastException)).Number + " Error: " + sqlErrors + " Retry Count: " + e.CurrentRetryCount, "Warning");
            }
            else
            {
                System.Diagnostics.Trace.WriteLine("Retrying Command: Last Exception: " + e.LastException + " Retry Count: " + e.CurrentRetryCount, "Warning");

            }
            states[0] = TrialState.probation;
        }
        static void ConnRetryPolicy_Retrying(object sender, Microsoft.Practices.TransientFaultHandling.RetryingEventArgs e)
        {
            if (e.LastException is SqlException)
            {
                string sqlErrors = "";
                foreach(SqlError err in ((SqlException)(e.LastException)).Errors)
                    sqlErrors+=@"Number: "+err.Number+@" Msg: " + err.Message+ @"\n";
                System.Diagnostics.Trace.WriteLine(@"Retrying Connection, Failed connecting to: "+((SqlException)(e.LastException)).Server+ " Last Exception: " + ((SqlException)(e.LastException)).Number+" Error: "+sqlErrors+ " Retry Count: " + e.CurrentRetryCount, "Warning");
            }
            else
            {
                System.Diagnostics.Trace.WriteLine(@"Retrying Connection Last Exception: " + e.LastException + " Retry Count: " + e.CurrentRetryCount, "Warning");
            }
            states[0] = TrialState.probation;
        }
    }

}